#!/opt/perl/bin/perl -w

#########################################################################
#									#
# Name:		msyncp (Perl Script)					#
#									#
# Args:		--config xmlfile [--verbose] [--comment X] 		#
#		[--noprepare] [--tag|--tagifchanged] [--nodelete]	#
#		[--usetag X]						#
#									#
#		--config	Name of the XML file containing the	#
#				configuration of the comparision/	#
#				synchronisation that must be performed.	#
#		--verbose	Show verbose messages.			#
#		--comment	Comment to use - typically just set to	#
#				number of lines changed.		#
#		--tag		Following the updates create a tag.	#
#		--usetag	Indicates the tag to use - if not	#
#				specified will default to YYYYMMDD.	#
#		--tagifchanged	Following the updates create a tag, if	#
#				any of the files have been changed or	#
#				new files have been checked in.		#
#		--nodelete	If a file is deleted in production	#
#				do not delete from project to sync to.	#
#		--noprepare	Do not run the remote prepare command	#
#				if this configuration has one - use	#
#				with caution.				#
#									#
# Environment:	NONE		The XML should ensure all necessary	#
#				environment configuration takes place.	#
#									#
# Purpose:	Compares a local project against a version of the	#
#		code (basic directories, no "project" environment), 	#
#		and flows updates INTO RCS PROJECT version only.	#
#									#
#		It does this by transferring all software across and	#
#		checking in any that has changed and adding new files	#
#		as necessary.						#
#									#
#		It is also possible to clean the production code	#
#		prior to comparision, and it will also ignore files	#
#		according to the extensions configured into the 	#
#		project.						#
#									#
#########################################################################

#########################################################################
# Normally we would do the following, but this must be evaluated only	#
# once the XML has been parsed successfully and any necessary 		#
# environment set.							#
#########################################################################

use File::Find;
use File::Basename;
use Getopt::Long;
use Cwd;
use English;
use XML::Parser;
use Data::Dumper;
use Digest::MD5;


$|=1;
umask 002;
$ARG_tag=0;
$ARG_tagifchanged=0;
$ARG_config=undef;
$ARG_comment=undef;
$ARG_usetag=undef;
$ARG_verbose=0;
$ARG_noprepare=0;
$ARG_nodelete=0;
$ARG_tmpdir="/tmp";
$CHANGES=0;
$_PRG=basename($0);
$_SPC=" " x length($_PRG);

sub are_same($$) {
my ($f1,$f2)=@_;
my ($md51,$md52,$fd1,$fd2);

	open($fd1,"$f1") or errormsg("Unable to open file \"$f1\": $!",1);
	open($fd2,"$f2") or errormsg("Unable to open file \"$f2\": $!",1);
	$md51=Digest::MD5->new;
	$md52=Digest::MD5->new;
	$md51->addfile($fd1);
	$md52->addfile($fd2);
	close($fd1); close($fd2);

	return(1) if $md51->hexdigest eq $md52->hexdigest;
	return 0;
}

sub usage {
	print
"Usage: $_PRG --config xmlfile [--verbose] [--comment X] [--nodelete] 
        $_SPC [--tag|--tagifchanged] [--noprepare] [--usetag X] [--tmpdir D]

";
	exit 1;
}

if(!GetOptions(
	"config|C=s"	=> \$ARG_config,
	"nodelete"	=> \$ARG_nodelete,
	"verbose|V"	=> \$ARG_verbose,
	"noprepare"	=> \$ARG_noprepare,
	"comment=s"	=> \$ARG_comment,
	"tmpdir=s"	=> \$ARG_tmpdir,
	"usetag=s"	=> \$ARG_usetag,
	"tag|T"		=> \$ARG_tag,
	"tagifchanged"	=> \$ARG_tagifchanged,
	)) {
	usage;
}

if(!defined($ARG_config)) { usage; }

if(! -f $ARG_config) {
	print STDERR "ERROR: File \"$ARG_config\" does not exist, or is not a file.\n";
	exit 1;
}
if($ARG_tag+$ARG_tagifchanged>1) {
	usage;
}

#########################################################################
# XML event parsing routines first...					#
#########################################################################

sub _handle_start {
shift;	
my $celement=shift;
$celement=lc($celement);
%attrs=@_;

	$data="";
}

sub _handle_end {
my $expat=shift;
my $celement=shift;
my @ss=qw(host source_dir dest_project projects_root prepare_remote ignore_program clean_sources environment msyncp gtar_program mget_program mco_program mci_program tag_program get_dir_list_remote);
my @aa=qw(cleaner var);

	$celement=lc($celement);
	#################################################################
	# Simple elements have _VALUE_ and _ATTRS_ (reference).		#
	#################################################################

	for (@ss) {
		if($_ eq $celement) {
			if(exists($CONFIG{$celement})) {
				print STDERR "ERROR: Element \"$celement\" defined multiple times!\n";
				exit 1;
			}
			my %e;
			$CONFIG{$celement}=[];
			push @{$CONFIG{$celement}},{};
			$CONFIG{$celement}->[0]->{_VALUE_}=$data;
			$e{$_}=$attrs{$_} foreach (keys(%attrs));
			$CONFIG{$celement}->[0]->{_ATTRS_}=\%e;
			return;
		}
	}

	for (@aa) {
		if($_ eq $celement) {
			if(!exists($CONFIG{$celement})) {
				$CONFIG{$celement}=[];
			}
			my %e;
			$e{_VALUE_}=$data;
			$e{_ATTRS_}={};
			$e{_ATTRS_}->{$_}=$attrs{$_} foreach (keys(%attrs));
			push @{$CONFIG{$celement}},\%e;
			return;
		}
	}

	#################################################################
	# If we get here have an unknown element, so emit warning.	#
	#################################################################

	print STDERR "WARNING: Unknown element \"$celement\" - ignoring.\n";
}
sub _handle_char {
	$data.=$_[1];
}

#########################################################################
# Parse the XML as the first step before doing anything else.		#
#########################################################################

%CONFIG=();
$P=undef;
$P=new XML::Parser(
		Handlers => {	Start	=> \&_handle_start,
				End	=> \&_handle_end,
				Char	=> \&_handle_char},
		ErrorContext => 2,
	);

eval {$P->parsefile($ARG_config);};
if($@) {
	print STDERR "ERROR: XML parse error: $@\n";
	exit 1;
}

#########################################################################
# Check the necessary (non-optional) elements have been defined, and	#
# if not abort.								#
#########################################################################


@m=();
for (qw(host source_dir dest_project projects_root)) {
	if(!exists($CONFIG{$_})) {
		push @m,$_;
	}
}

if(@m) {
	print STDERR "ERROR: Missing the following mandatory elements from config file:\n";
	print STDERR "ERROR: $_\n" foreach(@m);
	exit 1;
}

#########################################################################
# Before attempting to source any codemgr libraries, set up the 	#
# environment and then require the code.				#
#########################################################################

if($CONFIG{var}) {
	for (@{$CONFIG{var}}) {
		if(!exists($_->{_ATTRS_}->{name})) {
			print STDERR "WARNING: Missing attribute \"name\" from \"environment/var\" element.\n";
			next;
		}
		$name=$_->{_ATTRS_}->{name};
		$val=$_->{_VALUE_};
		if(substr($val,0,1) eq "+") {
			if(exists($ENV{$name})) {
				$ENV{$name}.=substr($val,1);
			} else {
				$ENV{$name}=substr($val,1);
			}
		} else {
			$ENV{$name}=$val;
		}
		print "Log  : Change/set of Environment var $name complete.\n" if $ARG_verbose;
	}
}

#########################################################################
# Attempt to get codemgr2 libs now environment is set.			#
#########################################################################

eval 'BEGIN {push @INC,$ENV{CODEMGR_LIBS} if exists($ENV{CODEMGR_LIBS});}
	use codemgr2 qw(make_dir_path logmsg warnmsg errormsg parse_rlog load_config get_project
		copyfile check_n_create_tdir attempt_alias_map);';

if($@) {
	print STDERR "Unable to load in \"codemgr2\" library!:\n";
	print STDERR $@,"\n";
	exit 1;
}

#########################################################################
# Set the projects root before continuing...				#
#########################################################################

$ENV{PROJECTS_ROOT}=$CONFIG{projects_root}->[0]->{_VALUE_};

$ARG_project=$CONFIG{dest_project}->[0]->{_VALUE_};

#########################################################################
# Make use the PROJECTS_ROOT directory exists...			#
#########################################################################

if(!exists($ENV{PROJECTS_ROOT})) {
	errormsg("Required environment variable \"PROJECTS_ROOT\" is not set.",3);
}

$PROJECTS_ROOT=$ENV{PROJECTS_ROOT};

if(! -d $ENV{PROJECTS_ROOT}) {
	errormsg("\"PROJECTS_ROOT\" directory \"$PROJECTS_ROOT\" does not exist.",2);
}

$PROJECT_SOURCE="$PROJECTS_ROOT/$ARG_project";
$PROJECT_META="$PROJECT_SOURCE/.meta";
$PROJECT_RELEASES="$PROJECT_META/releases";

if(! -d $PROJECT_SOURCE) {
	$alias=$ARG_project;
	$realname=attempt_alias_map($ARG_project);
	if(defined($realname)) {
		$ARG_project=$realname;
		$PROJECT_SOURCE="$PROJECTS_ROOT/$ARG_project";
		$PROJECT_META="$PROJECT_SOURCE/.meta";
		$PROJECT_RELEASES="$PROJECT_META/releases";
		logmsg("Matched project \"$ARG_project\" via alias \"$alias\".");
	} else {
		errormsg("Project source directory \"$PROJECT_SOURCE\" does not exist.",2);
	}
}

if(! -d $PROJECT_META) {
	errormsg("Project meta directory \"$PROJECT_META\" does not exist.",2);
}

#########################################################################
# Load in the config data ... which we need to get the SOURCE_DIR	#
# settings.								#
#########################################################################

logmsg("Loading project source configuration for project \"$ARG_project\".");
$cfg=load_config($PROJECT_META);
errormsg("Errors whilst loading source configuration - aborting.",3) if !defined($cfg);

if(!exists($cfg->{SOURCE_DIR})) {
	warnmsg("No source code directories defined for project!");
	exit(0);
}

if(!exists($cfg->{SOURCE_ROOT})) {
	errormsg("No \"SOURCE_ROOT\" defined in project configuration!",4);
}

#########################################################################
# Build up ssh command to be to perform commands/get data.		#
#########################################################################

$TYPE="?";
$PROG="";
$GTAR="";
$MGET="";
$MCO="";
$MCI="";
$TAG="";
if(exists($CONFIG{host}->[0]->{_ATTRS_}->{user})) {
	$REMOTE_ID=$CONFIG{host}->[0]->{_ATTRS_}->{user} . "@";
}
if(exists($CONFIG{gtar_program})) {
	$GTAR=$CONFIG{gtar_program}->[0]->{_VALUE_};
}
if(exists($CONFIG{mget_program})) {
	$MGET=$CONFIG{mget_program}->[0]->{_VALUE_};
}
if(exists($CONFIG{mco_program})) {
	$MCO=$CONFIG{mco_program}->[0]->{_VALUE_};
}
if(exists($CONFIG{mci_program})) {
	$MCI=$CONFIG{mci_program}->[0]->{_VALUE_};
}
if(exists($CONFIG{tag_program})) {
	$TAG=$CONFIG{tag_program}->[0]->{_VALUE_};
}

if(exists($CONFIG{host}->[0]->{_ATTRS_}->{type})) {
	$TYPE=$CONFIG{host}->[0]->{_ATTRS_}->{type};
}
if(exists($CONFIG{host}->[0]->{_ATTRS_}->{path})) {
	$PROG=$CONFIG{host}->[0]->{_ATTRS_}->{path};
}

$HOST=$CONFIG{host}->[0]->{_VALUE_};
$REMOTE_ID.=$HOST;

logmsg("Connection to host $HOST will be via protocol/Connection : $TYPE/$REMOTE_ID.");
logmsg("Explicit program for $TYPE        commmand is : $PROG") if length($PROG);
logmsg("Explicit program for gtar       commmand is : $GTAR") if length($GTAR);
logmsg("Explicit program for mget       commmand is : $MGET") if length($MGET);
logmsg("Explicit program for mco        commmand is : $MCO") if length($MCO);
logmsg("Explicit program for mci        commmand is : $MCI") if length($MCI);
logmsg("Explicit program for tagrelease commmand is : $TAG") if length($TAG);
logmsg("Working temporary directory will be         : $ARG_tmpdir");

$REMOTE_CMD="$PROG" if length($PROG);
$GTAR="gtar" if !length($GTAR);
$MGET="mget" if !length($MGET);
$MCO="mco" if !length($MCO);
$MCI="mci" if !length($MCI);
$REMOTE_CMD="$TYPE" if !length($PROG);

$r=`$REMOTE_CMD $REMOTE_ID echo hello 2>/dev/null`;
chomp $r;
if($r ne "hello") {
	errormsg("Communication failure using '$REMOTE_CMD $REMOTE_ID'.",2);
}

logmsg("Validated remote connectivity via $TYPE/$REMOTE_ID.");

#########################################################################
# Mow for each directory on the remote host, grab the contents to a 	#
# temporary local drive. Then use the "ignore_suffix" list in the	#
# project configuration to take out any files that are not considered	#
# relevant. 								#
# If the ignore_program is available then pass the list of remaining	#
# files through this to get a definitive list of files to process in	#
# the temporary directory.						#
#########################################################################

$TMPDIR="$ARG_tmpdir/tmpdir-$$";
if(! -d $TMPDIR) {
	mkdir $TMPDIR or errormsg("Unable to create directory: $TMPDIR: $!",1);
}

#########################################################################
# If a prepare_remote config option has been selected, then run it	#
# now...								#
#########################################################################

if(exists($CONFIG{prepare_remote}) && $ARG_noprepare==0) {
	logmsg("Running remote prepare command [" . $CONFIG{prepare_remote}->[0]->{_VALUE_} . "].");
	system("$REMOTE_CMD $REMOTE_ID " . $CONFIG{prepare_remote}->[0]->{_VALUE_} . ">/dev/null");
	$rc=$?>>8;
	if($rc) {
		warnmsg("Return code from remote prepare was $rc.");
	}
}

#########################################################################
# If a get_dir_list_remote entry exists then run it. We take the	#
# list of directories and update the conf file with any new directories.#
# Older ones are kept since they will still contain source code.	#
#########################################################################

if(exists($CONFIG{get_dir_list_remote})) {
	logmsg("Running remote dirlist command [" . $CONFIG{get_dir_list_remote}->[0]->{_VALUE_} . "].");

	my $dlist=`$REMOTE_CMD $REMOTE_ID $CONFIG{get_dir_list_remote}->[0]->{_VALUE_}`;
	if(defined($dlist)) {
		my @dirs=split(/\n/,$dlist);
		my %dirs_hash=();
		$dirs_hash{$_}=1 foreach(@dirs);
		#########################################################
		# Scan list of directories in project definition and	#
		# any are missing keep a note.				#
		#########################################################
		my @missing=();
		for my $c_remote_dir (@dirs) {
			push @missing,$c_remote_dir if(! grep {$_ eq $c_remote_dir} @{$cfg->{SOURCE_DIR}});
		}
		if(@missing) {
			my $xxx;
			open($xxx,">>$PROJECT_META/conf");
			for my $cdir (@missing) {
				logmsg("Adding directory '$cdir' to list of managed directories.");
				push @{$cfg->{SOURCE_DIR}},$cdir;
				print $xxx "SOURCE_DIR:$cdir\n";
			}
			close($xxx);
		}
	}
}

$REMOTE_SOURCE_ROOT=$CONFIG{source_dir}->[0]->{_VALUE_};
$LOCAL_SOURCE_ROOT=$cfg->{SOURCE_ROOT}->[0];
if(! -d $LOCAL_SOURCE_ROOT) {
	check_n_create_tdir($LOCAL_SOURCE_ROOT,$cfg); 
}

for $c_remote_dir (@{$cfg->{SOURCE_DIR}}) {
	$c_remote_path="$REMOTE_SOURCE_ROOT/$c_remote_dir";
	$c_remote_path =~ s/\/{2,}/\//g;

	logmsg("Retrieving files from $c_remote_path -> $TMPDIR...");
	$cmd="$REMOTE_CMD $REMOTE_ID \"cd $c_remote_path && $GTAR --no-recursion -cf - *\" | ( cd $TMPDIR && tar xf - )";
	`$cmd >/dev/null 2>/dev/null`;
	logmsg("...Done.");
	logmsg("  Removing files not considered relevant [IGNORE_SUFFIXES]...");
	@FF=();
	opendir(DFD,$TMPDIR);
	while($cent=readdir(DFD)) {
		next if ! -f "$TMPDIR/$cent"; 
		$r=0;
		@SUF=split(/,/,$cfg->{IGNORE_SUFFIXES}->[0]);
		for (@SUF) {
			my $suf=$_;
			if($cent =~ /$suf/) {
				$r=1; 
				logmsg("Ignoring '$cent' [matches '$suf']");
				last;
			}
		}
		push @FF,$cent if ! $r;
	} 
	closedir(DFD);
	logmsg("...Done.");

	#################################################################
	# If the ignore_program is set and it exists and it is 		#
	# executable get the real list via that program.		#
	#################################################################
	BLOCK1: {
		if(exists($CONFIG{ignore_program}->[0]->{_VALUE_})) {
			my $x=$CONFIG{ignore_program}->[0]->{_VALUE_};
			logmsg("  Pass list through ignore program [$x]...");
			if(! -f $x) {
				warnmsg("  Program \"$x\" does not exist.");
				last BLOCK1;
			}
			if(! -x $x) {
				warnmsg("  Program \"$x\" is not executable.");
				last BLOCK1;
			}
			open($wfd,">$ARG_tmpdir/msyncd-$$") or 
				errormsg("Unable to create file $ARG_tmpdir/msyncd-$$",1);
			print $wfd join("\n",@FF),"\n";
			close($wfd);
			$r=`$x $ARG_tmpdir/msyncd-$$ $c_remote_dir 2>/dev/null`;
			if($?>>8) {
				warnmsg("Failure indicated from \"$x\" - ignoring results.");
				last BLOCK1;
			}
			@FF=split(/\n/,$r);
			logmsg("...Done.");
		}
	}

	#################################################################
	# @FF is the list of files that need to be checked, so get the	#
	# list from the equivalent source directory.			#
	#################################################################

	@LL=(); @oLL=();
	#################################################################
	# If the local directory does not exist then create it if 	#
	# possible...							#
	#################################################################

	if(! -d "$LOCAL_SOURCE_ROOT/$c_remote_dir") {
		make_dir_path("$LOCAL_SOURCE_ROOT/$c_remote_dir"); 
		$owner="root" if !defined($owner);
		$group="sys" if !defined($group);
		$perms="0775" if !defined($perms);

		system("chmod $perms $LOCAL_SOURCE_ROOT/$c_remote_dir");
		system("chgrp $group $LOCAL_SOURCE_ROOT/$c_remote_dir") if $owner eq "SAME";
		system("chown $owner:$group $LOCAL_SOURCE_ROOT/$c_remote_dir") if $owner ne "SAME";
	}
	if(!opendir(DFD,"$LOCAL_SOURCE_ROOT/$c_remote_dir")) {
		errormsg("Unable to open \"$LOCAL_SOURCE_ROOT/$c_remote_dir\" - ignoring results.");
		next;
	}
	while($cent=readdir(DFD)) {
		next if ! -f "$LOCAL_SOURCE_ROOT/$c_remote_dir/$cent"; 
		push @oLL,$cent;
		$r=0;
		@SUF=split(/,/,$cfg->{IGNORE_SUFFIXES}->[0]);
		for (@SUF) {
			my $suf=$_;
			my ($j,$s)=($cent =~ /(.*)\.(.*)$/);
			if(defined($s) && $s eq $suf) {
				$r=1; last;
			}
		}
		push @LL,$cent if ! $r;
	} 
	closedir(DFD);
	
	#################################################################
	# If the ignore_program is set and it exists and it is 		#
	# executable get the real list via that program.		#
	#################################################################
	BLOCK2: {
		if(exists($CONFIG{ignore_program}->[0]->{_VALUE_})) {
			logmsg("  Pass list through ignore program ...");
			my $x=$CONFIG{ignore_program}->[0]->{_VALUE_};
			if(! -f $x) {
				warnmsg("  Program \"$x\" does not exist.");
				last BLOCK2;
			}
			if(! -x $x) {
				warnmsg("  Program \"$x\" is not executable.");
				last BLOCK2;
			}
			open($wfd,">$ARG_tmpdir/msyncd-$$") or 
				errormsg("Unable to create file $ARG_tmpdir/msyncd-$$",1);
			print $wfd join("\n",@LL),"\n";
			close($wfd);
			$r=`$x $ARG_tmpdir/msyncd-$$ $c_remote_dir 2>/dev/null`;
			if($?>>8) {
				warnmsg("  Failure indicated from \"$x\" - ignoring results.");
				last BLOCK2;
			}
			@LL=split(/\n/,$r);
			logmsg("...Done.");
		}
	}

	#################################################################
	# Now for each file in TMPDIR is checked to see if a code	#
	# cleaner exists for the type and if so all code is passed	#
	# through it first...						#
	#################################################################

	if(exists($CONFIG{cleaner})) {
		foreach $cref (@{$CONFIG{cleaner}}) {
			my ($ext,$pat);
			if(!exists($cref->{_ATTRS_}->{ext})) { next; }
			$cmd=$cref->{_VALUE_};	
			$ext="*." . $cref->{_ATTRS_}->{ext};
			$cmd =~ s/_EXT_/$ext/g;
			logmsg("Cleaning all files matching \"$ext\" ...");
			`cd $TMPDIR && $cmd >/dev/null`;
			if($?>>8) {
				errormsg("Unable to clean files - aborting update.",1);
			}
			logmsg("...Done.");
		}

	}

	#################################################################
	# Firstly deal with any programs that exist in the @LL but not	#
	# in @FF - they should be deleted unless --nodelete is passed.	#
	#################################################################

	foreach (@oLL) {
		my $f=$_;
		if(!scalar(grep {/^$f$/} @FF)) {
			logmsg("Need to remove $f - does not exist remotely.");
			if(! $ARG_nodelete) {
				unlink "$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
			}
			next;
		}
	}

	#################################################################
	# Now check for files that exist remotely but do not exist	#
	# locally. These are either new files, or existing files that	#
	# have previously been removed, but appeared again...		#
	#################################################################

	for (@FF) {
		my ($owner,$group,$perms);
		my $f=$_;
		if(!scalar(grep {/^$f$/} @LL)) {
			logmsg("Need to add $f - does not exist locally.");
			$cmd="cd $LOCAL_SOURCE_ROOT/$c_remote_dir && $MGET --force $f\n";
			`$cmd >/dev/null`;
			if(! -f "$LOCAL_SOURCE_ROOT/$c_remote_dir/$f") {
				copyfile("$TMPDIR/$f","$LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
			} else {
				chmod 0444,"$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
				$cmd="cd $LOCAL_SOURCE_ROOT/$c_remote_dir && chmod 444 $f && $MCO --force $f";
				print "Running: $cmd\n";
				`$cmd >/dev/null`;
				if($?>>8) {
					warnmsg("  Unable to check out file $c_remote_dir/$f- already out?");
				}
				unlink "$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
				copyfile("$TMPDIR/$f","$LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
				@sb=stat("$LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
				$mm=$sb[2]&07777;
				$mm&=07555;
				chmod $mm,"$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
			}
			if(-f "$LOCAL_SOURCE_ROOT/$c_remote_dir/$f") {
				#########################################
				# File has been modified so check it	#
				# back in.				#
				#########################################
				$cmd="cd $LOCAL_SOURCE_ROOT/$c_remote_dir && $MCI --force --msg X $f\n";
				`$cmd >/dev/null`;
				if($?>>8) {
					errormsg("Unable to check in file $c_remote_dir/$f - aborting[2]!",1);
				} else {
					$CHANGES++;
				}
				if(exists($cfg->{NEW_CODE_DIR_PERMS})) {
					($owner,$group,$perms)=split(/,/,(@{$cfg->{NEW_CODE_DIR_PERMS}})[0]);
				}
				$owner="root" if !defined($owner);
				$group="sys" if !defined($group);
				$perms="0775" if !defined($perms);

				# system("chmod $perms $LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
				system("chgrp $group $LOCAL_SOURCE_ROOT/$c_remote_dir/$f") if $owner eq "SAME";
				system("chown $owner:$group $LOCAL_SOURCE_ROOT/$c_remote_dir/$f") if $owner ne "SAME";
				logmsg("  Updated local version of $c_remote_dir/$f successfully.");
			}
		}
	}

	#################################################################
	# If the file exists in both the local and remote copies then	#
	# if they are the same then do nothing, otherwise perform a	#
	# mco / copyfile and mci to push back the new version under	#
	# code control.							#
	#################################################################

	$first_local_dir="$LOCAL_SOURCE_ROOT/$c_remote_dir" if !defined($first_local_dir);
	for $f (@FF) {
		if(are_same("$TMPDIR/$f","$LOCAL_SOURCE_ROOT/$c_remote_dir/$f")) {
			next;
		}
		chmod 0444,"$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
		$cmd="cd $LOCAL_SOURCE_ROOT/$c_remote_dir && chmod 444 $f && $MCO --force $f";
		print "Running: $cmd\n";
		`$cmd 2>/dev/null`;
		if($?>>8) {
			warnmsg("  Unable to check out file $c_remote_dir/$f - already out?");
		}
		if(!are_same("$TMPDIR/$f","$LOCAL_SOURCE_ROOT/$c_remote_dir/$f")) {
			unlink "$LOCAL_SOURCE_ROOT/$c_remote_dir/$f";
			copyfile("$TMPDIR/$f","$LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
		} 
		$cmd="cd $LOCAL_SOURCE_ROOT/$c_remote_dir && $MCI --force --msg X $f\n";
		system("$cmd >/dev/null");
		if($?>>8) {
			errormsg("Unable to check in file $c_remote_dir/$f - aborting[1]!",1);
		} else {
			$CHANGES++;
		}
		if(exists($cfg->{NEW_CODE_DIR_PERMS})) {
			($owner,$group,$perms)=split(/,/,(@{$cfg->{NEW_CODE_DIR_PERMS}})[0]);
		}
		$owner="root" if !defined($owner);
		$group="sys" if !defined($group);
		$perms="0775" if !defined($perms);

		# system("chmod $perms $LOCAL_SOURCE_ROOT/$c_remote_dir/$f");
		system("chgrp $group $LOCAL_SOURCE_ROOT/$c_remote_dir/$f") if $owner eq "SAME";
		system("chown $owner:$group $LOCAL_SOURCE_ROOT/$c_remote_dir/$f") if $owner ne "SAME";
		logmsg("  Updated local version of $c_remote_dir/$f successfully.");
	}

	#################################################################
	# At this point the contents of the temporary directory can be	#
	# removed...							#
	#################################################################

	if(opendir(DFD,$TMPDIR)) {
		@FF=readdir(DFD); closedir(DFD);
		for (@FF) {
			next if $_ eq "." || $_ eq "..";
			rmdir "$TMPDIR/$_" if -d "$TMPDIR/$_";
			unlink "$TMPDIR/$_" if -f "$TMPDIR/$_" || -l "$TMPDIR/$_";
		}
	} else {
		errormsg("Unable to read contents of $TMPDIR - aborting.",1);
	}
	#########################################################
	# Double check all files in the directory in the 	#
	# project we scanned are now read only.			#
	#########################################################
	{
		my $dfd;
		opendir($dfd,"$LOCAL_SOURCE_ROOT/$c_remote_dir");
		if(defined($dfd)) {
			my $cfile;
			while($cfile=readdir($dfd)) {
				if(-f "$LOCAL_SOURCE_ROOT/$c_remote_dir/$cfile") {
					chmod 0444,"$LOCAL_SOURCE_ROOT/$c_remote_dir/$cfile";
				}
			}
			closedir($dfd);
		}
	}
}

if(!defined($first_local_dir)) {
	rmdir($TMPDIR);
	errormsg("Project \"$ARG_project\" has no source directories!",1);
}

if($ARG_tag || ($CHANGES>0 && $ARG_tagifchanged)) {
	@D=localtime(time);
	if(!defined($ARG_usetag)) {
		$fmt=sprintf("%04d%02d%02d",$D[5]+1900,$D[4]+1,$D[3]);
	} else {
		$fmt=$ARG_usetag;
	}
	$fmt2=sprintf("%02d/%02d/%04d",$D[3],1+$D[4],$D[5]+1900);
	$cmd="$TAG --tag  $fmt --force --desc 'Auto created on $fmt2'";
	logmsg("Creating tag using command:");
	logmsg("$cmd");
	`cd $first_local_dir && $cmd`;
	if($?>>8) {
		errormsg("Unable to run command \"$cmd\" - no tag created!",1);
	} else {
		logmsg("Tag $fmt created successfully.");
	}
}
logmsg("Auto update of project \"$ARG_project\" completed.");
if(-d $TMPDIR) {
	opendir(DFD,$TMPDIR);
	while($cent=readdir(DFD)) {
		unlink("$TMPDIR/$cent") if -f "$TMPDIR/$cent";
	}
	closedir(DFD);
	rmdir($TMPDIR);
}
unlink("$ARG_tmpdir/msyncd-$$") if -f "$ARG_tmpdir/msyncd-$$";
exit(0);
